Skip to content

[core] Search primary-key vector buckets and files concurrently#556

Merged
JingsongLi merged 2 commits into
apache:mainfrom
JunRuiLee:feat/pk-vector-parallel-batch
Jul 22, 2026
Merged

[core] Search primary-key vector buckets and files concurrently#556
JingsongLi merged 2 commits into
apache:mainfrom
JunRuiLee:feat/pk-vector-parallel-batch

Conversation

@JunRuiLee

@JunRuiLee JunRuiLee commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Purpose

Part of #514, and the last of three PRs split out of the original combined change on the primary-key vector execute_read path (after #559 streaming exact fallback and #560 batch multi-query, both now on main). This PR adds intra-search parallelism: a configurable concurrency limit fans the per-bucket and per-exact-file searches out, without changing results.

Brief change log

  • perf(table): a global-index.thread-num option (default 32, rejected when non-positive) threads a concurrency limit through the search. The per-bucket loop (orchestrator) and per-exact-file loop (bucket search) fan out with buffer_unordered up to that limit; a limit of 1 takes a plain sequential loop so visit order stays deterministic. Results are accumulated by fixed query and split index and merged through the existing order-independent bounded heaps and global top-k, so output does not depend on completion order. The ANN segment scan stays sequential (synchronous CPU work).
  • refactor(table): the exclusion predicate threaded through the search (residual filter ∩ deletion vectors) is marked Send + Sync so the search future is Send and can run on a multi-threaded runtime.

Tests

  • Sequential call order asserted at both layers for concurrency 1; parallel (concurrency > 1) with tied/NaN/equal-score candidates completing out of order produces the deterministic ranking equal to the serial result.
  • global-index.thread-num default/explicit/non-positive.
  • cargo test -p paimon --lib green (1793 passed); cargo clippy -p paimon --lib --tests and cargo fmt --check clean.

API and Format

No on-disk format change, no new result columns. The concurrency limit defaults to a value that does not change results, only scheduling. New public surface: the global-index.thread-num option (matches Java Paimon's option name, semantics, and default of 32).

Documentation

Code comments only; no user-facing docs change.

@JunRuiLee
JunRuiLee force-pushed the feat/pk-vector-parallel-batch branch 2 times, most recently from eb6bc77 to f27a56d Compare July 20, 2026 12:27
@JunRuiLee JunRuiLee changed the title [core] Batch, parallel, and streaming for primary-key vector search [core] Search primary-key vector buckets and files concurrently Jul 20, 2026
@JunRuiLee
JunRuiLee force-pushed the feat/pk-vector-parallel-batch branch from f27a56d to 2836ae1 Compare July 21, 2026 14:10
Add a global-index.thread-num option (default 32, rejected when non-positive)
and thread its value as a concurrency limit through the primary-key vector
search. The per-bucket loop in the orchestrator and the exact-fallback per-file
loop in the bucket search now fan out with buffer_unordered up to that limit;
a limit of 1 takes a plain sequential loop so file and bucket visit order stay
deterministic. Results are accumulated by fixed query and split index and
merged through the existing order-independent bounded heaps and global top-k,
so the output does not depend on completion order. The ANN segment scan stays
sequential because it is synchronous CPU work.
@JunRuiLee
JunRuiLee force-pushed the feat/pk-vector-parallel-batch branch from 2836ae1 to 913b162 Compare July 22, 2026 03:32
@JunRuiLee
JunRuiLee marked this pull request as ready for review July 22, 2026 03:34
@JingsongLi

Copy link
Copy Markdown
Contributor

global-index.thread-num is supposed to be the maximum concurrency for global-index I/O, but the same value is applied independently at both levels: up to N bucket futures here, and each bucket starts up to N exact-file futures in bucket_search_batch. Therefore, with the default N=32, a single search can issue up to 1024 exact-file searches concurrently, and concurrent queries multiply this further. This differs from Java's shared GlobalIndexReadThreadPool, which caps the whole search at N. I reproduced this with two buckets, two files per bucket, and concurrency=2; four file searches were active simultaneously. Could we use one shared semaphore/concurrency budget across both levels, or only fan out at one level, and add a test that verifies the peak concurrency?

global-index.thread-num was applied independently at both fan-out
levels: up to N bucket futures, and each bucket started up to N
exact-file futures, so a single search could run up to N*N concurrent
exact-file searches (and concurrent queries multiplied it further).

Mirror Java's single shared GlobalIndexReadThreadPool by threading one
Arc<Semaphore> of N permits through the whole search and acquiring it
only around leaf exact-file I/O. Bucket orchestration never holds a
permit, so it cannot starve leaf work (the async analogue of Java's
"start from the caller" note in PrimaryKeyVectorRead.searchBuckets).
Total in-flight exact-file searches are now capped at N overall rather
than per bucket; the concurrency == 1 path stays strictly sequential.

Add search_candidates_peak_concurrency_capped_across_buckets: 2 buckets
x 2 files at concurrency 2 must observe peak in-flight <= N (was 4).
@JunRuiLee

Copy link
Copy Markdown
Contributor Author

Thanks @JingsongLi, you're exactly right — the limit was applied independently at each level, so a single search could reach N×N concurrent exact-file reads (and concurrent queries multiplied it further).

Fixed in 5293431. Instead of two independent buffer_unordered(N) limits, the search now threads a single shared Arc<Semaphore> of N permits through the whole search, mirroring Java's single shared GlobalIndexReadThreadPool. A permit is acquired only around leaf exact-file I/O; the per-bucket orchestration never holds one, so it can't starve leaf work — the async analogue of the "start from the caller" note in PrimaryKeyVectorRead.searchBuckets. Total in-flight exact-file searches are now capped at N overall rather than per bucket, and the concurrency == 1 path stays strictly sequential.

I also added search_candidates_peak_concurrency_capped_across_buckets, which reproduces your scenario (two buckets, two files each, concurrency = 2): it instruments the exact-file search to record the peak number of simultaneously active searches and asserts it stays ≤ N. I confirmed the test observes peak = 4 and fails without the shared budget, and peak = 2 (passing) with it.

@JingsongLi JingsongLi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@JingsongLi
JingsongLi merged commit fb86c9a into apache:main Jul 22, 2026
13 checks passed
@JunRuiLee
JunRuiLee deleted the feat/pk-vector-parallel-batch branch July 22, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants